﻿using FollowMe.AuthService;
using FollowMe.AuthService.Messages;
using FollowMe.AuthService.Views;
using FollowMe.Services;
using FollowMe.Services.Request;
using FollowMe.Services.Response;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.ModelBinding;

/**
 * FollowMe Server
 * UWRF CS425 & CS343 Project Fall 2014
 * Adam Drews
 *   email adam.drews@my.uwrf.edu
 * Tyler Johnson
 *   email tyler.johnson@my.uwrf.edu
 * Nicholas Thorp
 *   email nicholas.thorp@my.uwrf.edu
 */
namespace FollowMe.API.Controllers
{
    /// <summary>
    /// Account controller that uses OWIN/Katana.  Initial registration happens here.
    // This is largely MS defaults except for registration.
    /// </summary>
    [Authorize]
    [RoutePrefix("api/Account")]
    public class AccountController : ApiController
    {
        /// <summary>
        /// The local login provider
        /// </summary>
        private const string LocalLoginProvider = "Local";
        /// <summary>
        /// The user manager attached to http request
        /// </summary>
        private AuthUserManager _userManager;
        /// <summary>
        /// The follow me service layer
        /// </summary>
        private IFollowMeService followMeService;

        /// <summary>
        /// Initializes a new instance of the <see cref="AccountController"/> class.
        /// </summary>
        public AccountController()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AccountController"/> class.
        /// </summary>
        /// <param name="services">The services.</param>
        [InjectionConstructor]
        public AccountController(IFollowMeService services)
        {
            followMeService = services;
        }

        /// <summary>
        /// Gets the Owin user manager.
        /// </summary>
        /// <value>
        /// The user manager extracted from the request
        /// </value>
        public AuthUserManager UserManager
        {
            get
            {
                return _userManager ?? Request.GetOwinContext().GetUserManager<AuthUserManager>();
            }
            private set
            {
                _userManager = value;
            }
        }

        // GET api/Account/UserInfo
        /// <summary>
        /// Gets the user information.
        /// </summary>
        /// <returns></returns>
        [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
        [Route("UserInfo")]
        public UserInfoViewModel GetUserInfo()
        {
            return new UserInfoViewModel
            {
                Email = User.Identity.GetUserName(),
            };
        }

        // POST api/Account/Logout
        /// <summary>
        /// Logouts this instance.
        /// </summary>
        /// <returns></returns>
        [Route("Logout")]
        public IHttpActionResult Logout()
        {
            Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
            return Ok();
        }

        // GET api/Account/ManageInfo?returnUrl=%2F&generateState=true
        /// <summary>
        /// Gets the manage information.
        /// </summary>
        /// <param name="returnUrl">The return URL.</param>
        /// <param name="generateState">if set to <c>true</c> [generate state].</param>
        /// <returns></returns>
        [Route("ManageInfo")]
        public async Task<ManageInfoViewModel> GetManageInfo(string returnUrl, bool generateState = false)
        {
            IdentityUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());

            if (user == null)
            {
                return null;
            }

            List<UserLoginInfoViewModel> logins = new List<UserLoginInfoViewModel>();

            foreach (IdentityUserLogin linkedAccount in user.Logins)
            {
                logins.Add(new UserLoginInfoViewModel
                {
                    LoginProvider = linkedAccount.LoginProvider,
                    ProviderKey = linkedAccount.ProviderKey
                });
            }

            if (user.PasswordHash != null)
            {
                logins.Add(new UserLoginInfoViewModel
                {
                    LoginProvider = LocalLoginProvider,
                    ProviderKey = user.UserName,
                });
            }

            return new ManageInfoViewModel
            {
                LocalLoginProvider = LocalLoginProvider,
                Email = user.UserName,
                Logins = logins,
            };
        }

        // POST api/Account/ChangePassword
        /// <summary>
        /// Changes the password.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <returns></returns>
        [Route("ChangePassword")]
        public async Task<IHttpActionResult> ChangePassword(ChangePasswordBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            IdentityResult result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword,
                model.NewPassword);

            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }

            return Ok();
        }

        // POST api/Account/SetPassword
        /// <summary>
        /// Sets the password.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <returns></returns>
        [Route("SetPassword")]
        public async Task<IHttpActionResult> SetPassword(SetPasswordBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            IdentityResult result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);

            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }

            return Ok();
        }

        // POST api/Account/RemoveLogin
        /// <summary>
        /// Removes the login.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <returns></returns>
        [Route("RemoveLogin")]
        public async Task<IHttpActionResult> RemoveLogin(RemoveLoginBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            IdentityResult result;

            if (model.LoginProvider == LocalLoginProvider)
            {
                result = await UserManager.RemovePasswordAsync(User.Identity.GetUserId());
            }
            else
            {
                result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(),
                    new UserLoginInfo(model.LoginProvider, model.ProviderKey));
            }

            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }

            return Ok();
        }

        // POST api/Account/Register
        /// <summary>
        /// Registers the specified member.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>The registration results.  Either failed, or the MemberView</returns>
        [AllowAnonymous]
        [Route("Register")]
        public async Task<IHttpActionResult> Register(AuthRegisterMemberRequest member)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if(member.lastname == null || member.firstname == null || member.email == null || member.password == null)
            {
                return BadRequest(ModelState);
            }

            Guid guid = Guid.NewGuid();
            AuthUser user = new AuthUser(guid) { UserName = member.email, Email = member.email };
            IdentityResult task = await UserManager.CreateAsync(user, member.password);

            //AuthServices.register()

            if (!task.Succeeded)
            {
                return GetErrorResult(task);
            }


            RegisterMemberRequest registerMember = new RegisterMemberRequest();
            registerMember.Email = member.email;
            registerMember.userName = member.email;
            registerMember.memberid = guid;
            registerMember.firstname = member.firstname;
            registerMember.lastname = member.lastname;

            MemberView memberV = followMeService.addNewMember(registerMember);
            if (memberV == null)
            {
                var userB = UserManager.FindById(guid.ToString());

                IList<string> rolesForUser = await _userManager.GetRolesAsync(guid.ToString());

                foreach (string item in rolesForUser)
                {
                    var result = await _userManager.RemoveFromRoleAsync(user.Id, item);
                }

                IdentityResult taskD = await UserManager.DeleteAsync(userB);
                return BadRequest();
            };

            return Ok(memberV);
        }

        /// <summary>
        /// Releases the unmanaged resources that are used by the object and, optionally, releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                UserManager.Dispose();
            }

            base.Dispose(disposing);
        }

        #region Helpers

        /// <summary>
        /// Gets the authentication.
        /// </summary>
        /// <value>
        /// The authentication.
        /// </value>
        private IAuthenticationManager Authentication
        {
            get { return Request.GetOwinContext().Authentication; }
        }

        /// <summary>
        /// Gets the error result from user creation etc
        /// </summary>
        /// <param name="result">The result.</param>
        /// <returns></returns>
        private IHttpActionResult GetErrorResult(IdentityResult result)
        {
            if (result == null)
            {
                return InternalServerError();
            }

            if (!result.Succeeded)
            {
                if (result.Errors != null)
                {
                    foreach (string error in result.Errors)
                    {
                        ModelState.AddModelError("", error);
                    }
                }

                if (ModelState.IsValid)
                {
                    // No ModelState errors are available to send, so just return an empty BadRequest.
                    return BadRequest();
                }

                return BadRequest(ModelState);
            }

            return null;
        }
        #endregion
    }
}
